/*
    Single touch Touchpad demonstration for the "4" on the Experimenter's board.

    Last updated: Feb 2007

    Author: Steve Underwood

    Modified May 2011 by ALT
*/
#include  <stdint.h>
#include  <stdlib.h>
#include  <msp430x20x3.h>
#include  "F2013_touchpad.h"


/* global variables */
int lastPosition = 0; //previous key response
int newPosition = 0;  //current key response
uint8_t hostData = 0; //data to be sent to FG4618
unsigned int timerCount = 0;  //timer counter used in measureKeyCapacitance
int peak = -100;  //initial key response (before findFingerPosition is called)
long int tempKeyResponseOffset = 0; //offset on temporary key response
long int tempKeyResponse = 0; //temporary key response
int minKeyResponse = 0; //minimum key response
int grassLevel = 0; //noise level


/* This structure defines the static data to maintain one key sense line */
typedef struct
{
    unsigned char port;         // port 1 or 2
    unsigned char portBit;      // bit (0-7) for corresponding port
    int threshold;              
} keyLineConfigDataT;

/* This structure defines the working data to maintain one key sense line */
typedef struct
{
    int baseCapacitance;
    int filtered; //filtered capacitance
} keyLineDataT;

/* The port bit allocation for the 8 key sensing lines */
const keyLineConfigDataT keyLineConfig[NUM_LINES] =
{
    {1, BIT0, 40},
    {1, BIT1, 40},
    {1, BIT2, 40},
    {1, BIT3, 40},
    {1, BIT4, 40},
    {1, BIT5, 40},
    {2, BIT6, 40},
    {2, BIT7, 40},
};

///* The working data for the 8 sense lines */
keyLineDataT keyLine[NUM_LINES];

/* The key sequence mapping to the 8 sense lines */
const uint8_t mapKeyToSense[NUM_KEYS] =
{
    2, 3, 1, 6,
    5, 2, 7, 6,
    1, 4, 3, 0,
    5, 4, 0, 7
};

/* The sense line mapping to the associated pairs of keys */
const uint8_t mapSenseToKey[NUM_KEYS][2] =
{
  {11, 14},
  {2, 8},
  {0, 5},
  {1, 10},
  {9, 13},
  {4, 12},
  {3, 7},
  {6, 15}
};


/* function prototypes */
/* function to initialize keys */
void initKey
(
    keyLineDataT *keyLine,                    // one key sense line [input]
    const keyLineConfigDataT *keyLineConfig   // port allocation for key [input]
 );   
/* function to measure key capacitance */
//Inputs: keyNo = key number whose capacitance is being measured
//Outputs: sum = timer sum
unsigned int measureKeyCapacitance
(
    int keyNo  
);       
/* function to measure capacitance on all keys */
int scanKeys(void);
/* function to filter out noise from capacitance measurement */
void noiseFilter
(
    int line, //line number (0-7) [input]
    int key   //key number (0-15) [output]
);
/* function to find which key finger is on */
//Outputs: newKey = key number finger is on; keyResponse = key response
int findFingerPosition
(
    int*  newKey  
);
/* function to set up I2C communication */  
void usiI2cInit();
/* function to send data to FG4618 */
void sendToHost(int);


/* function to initialize keys */
//Inputs: key_line_config = port allocation for key
//Outputs: none
void initKey
(
     keyLineDataT *keyLine, //one key sense line [input]                      
     const keyLineConfigDataT *keyLineConfig  //port allocation for key [output]    
)
{
    keyLine->baseCapacitance = 0;
    keyLine->filtered = 0;

    /* Set the capacitive sense pin to output, low level, low going interupts */
    /* We need to keep all switches, except the one we are currently sensing,
       driven low so they form the "ground" against which the active pad and the
       finger form the variable capacitance. The end pads in the row can be
       expected to give a weaker response, because they are not surrounded by
       grounded keys on both sides, as the other keys are. */
    if (keyLineConfig->port == 1)
    {
        P1OUT &= ~keyLineConfig->portBit;
        P1DIR |= keyLineConfig->portBit;
        P1IES |= keyLineConfig->portBit;
    }
    else
    {
        P2OUT &= ~keyLineConfig->portBit;
        P2DIR |= keyLineConfig->portBit;
        P2IES |= keyLineConfig->portBit;
    }
}//end initKey

/* function to measure key capacitance */
//Inputs: keyNo = key number whose capacitance is being measured
//Outputs: sum = timer sum
unsigned int measureKeyCapacitance
(
    int keyNo
)
{
    char activeKey;
    const keyLineConfigDataT *keyp;
    const keyLineConfigDataT *partner;
    int sum;

    keyp = &keyLineConfig[keyNo];
    partner = &keyLineConfig[keyNo ^ 1];

    /* Right now, all keys should be driven low, forming a "ground" area */
    activeKey = keyp->portBit;
    /* Take the active key high to charge the pad */
    if (keyp->port == 1)
        P1OUT |= activeKey;
    else
        P2OUT |= activeKey;
    /* Allow a short time for the hard pull high to really charge the pad */
    _NOP();
    _NOP();
    _NOP();
    /* Enable interrupts (edge is already set to low going trigger)
       set the active key to input (was output high), and start the
       timed discharge of the pad. */
    if (keyp->port == 1)
    {
        P1IES |= activeKey;
        P1IE |= activeKey;
        P1DIR &= ~activeKey;
    }
    else
    {
        P2IES |= activeKey;
        P2IE |= activeKey;
        P2DIR &= ~activeKey;
    }
    /* Take a snaphot of the timer... */
    timerCount = TAR;
    /* ...and wait for the discharge to cause an interrupt. We wait in LPM0 mode, as we
       need the fast clock running to perform accurate timing of the discharge. We
       need to minimise noise disturbing the discharge time. We should, therefore, have
       all unnecessary peripherals idle. We do not want other interrupts spoiling the
       crispness of our reponse to the port interrupt, so all other interrupts should
       be turned off at this time. */
    LPM0;
    /* Return the key to the driven low state, to contribute to the "ground" area
       around the next key to be scanned. */
    if (keyp->port == 1)
    {
	P1IE &= ~activeKey;            /* disable active key interrupt */
        P1OUT &= ~activeKey;           /* switch active key to low to discharge the key */
    	P1DIR |= activeKey;            /* switch active key to output low to save power */
    }
    else
    {
	P2IE &= ~activeKey;            /* disable active key interrupt */
    	P2OUT &= ~activeKey;           /* switch active key to low to discharge the key */
    	P2DIR |= activeKey;            /* switch active key to output low to save power */
    }
    sum = timerCount;
    /* Right now, all keys should be driven low, forming a "ground" area */
    activeKey = keyp->portBit;
    /* Take the partner key high to charge the pad */
    if (partner->port == 1)
        P1OUT |= partner->portBit;
    else
        P2OUT |= partner->portBit;
    /* Allow a short time for the hard pull high to really charge the pad */
    _NOP();
    _NOP();
    _NOP();
    /* Enable interrupts (edge is already set to low going trigger)
       set the active key to input (was output high), and start the
       timed discharge of the pad. */
    if (keyp->port == 1)
    {
        P1IES &= ~activeKey;
        P1IE |= activeKey;
        P1DIR &= ~activeKey;
    }
    else
    {
        P2IES &= ~activeKey;
        P2IE |= activeKey;
        P2DIR &= ~activeKey;
    }
    /* Take a snaphot of the timer... */
    timerCount = TAR;
    /* ...and wait for the discharge to cause an interrupt. We wait in LPM0 mode, as we
       need the fast clock running to perform accurate timing of the discharge. We
       need to minimise noise disturbing the discharge time. We should, therefore, have
       all unnecessary peripherals idle. We do not want other interrupts spoiling the
       crispness of our reponse to the port interrupt, so all other interrupts should
       be turned off at this time. */
    LPM0;
    /* Return the key to the driven low state, to contribute to the "ground" area
       around the next key to be scanned. */
    if (keyp->port == 1)
    {
	P1IE &= ~activeKey;            /* disable active key interrupt */
        P1OUT &= ~activeKey;           /* switch active key to low to discharge the key */
    	P1DIR |= activeKey;            /* switch active key to output low to save power */
    }
    else
    {
	P2IE &= ~activeKey;            /* disable active key interrupt */
    	P2OUT &= ~activeKey;           /* switch active key to low to discharge the key */
    	P2DIR |= activeKey;            /* switch active key to output low to save power */
    }
    if (partner->port == 1)
        P1OUT &= ~partner->portBit;
    else
        P2OUT &= ~partner->portBit;
    sum += timerCount;
    return sum >> 1;
}//end measureKeyCapacitance

/* function to measure capacitance on all keys */
//Inputs: none
//Outputs: none
int scanKeys(void)
{
    int keyLineIndex;
    int margin;

    for (keyLineIndex = 0;  keyLineIndex < NUM_LINES;  keyLineIndex++)
    {
        margin = measureKeyCapacitance(keyLineIndex) - keyLine[keyLineIndex].baseCapacitance;
        keyLine[keyLineIndex].filtered += (margin - (keyLine[keyLineIndex].filtered >> 4));
    }
    return 0;
}//end scanKeys

/* function to filter out noise from capacitance measurement */
//Inputs: line = line number (0-7)
//Outputs: key = key number (0-15)
void noiseFilter
(
     int line, 
     int key
)
{
    if ((keyLine[line].filtered - minKeyResponse) > grassLevel)
    {
        tempKeyResponseOffset += (keyLine[line].filtered - minKeyResponse);
        tempKeyResponse += key*(keyLine[line].filtered - minKeyResponse);
    }
}//end noiseFilter

/* function to find which key finger is on */
//Inputs: none
//Outputs: newKey = key number finger is on; KeyResponse = key response
int findFingerPosition
(
    int*  newKey                        
)
{
    int keyLineIndex;
    int keyNoIndex;
    int keyNo1;
    int keyNo2;
    int keyNoOffset;
    int maxKeyNo;
    long int keyResponseOffset;
    long int keyResponse;
    int maxKeyResponse = -32768;
    int maxPos = -1;

    /* Find the minimum and maximum responses for the key lines */
    minKeyResponse = 32767;
    for (keyLineIndex = 0;  keyLineIndex < NUM_LINES;  keyLineIndex++)
    {
        if (keyLine[keyLineIndex].filtered < minKeyResponse)
            minKeyResponse = keyLine[keyLineIndex].filtered;
        if (keyLine[keyLineIndex].filtered > maxKeyResponse)
        {
            maxKeyResponse = keyLine[keyLineIndex].filtered;
            maxPos = keyLineIndex;
        }
    }
    /* If the maximum response isn't that big, there is no finger present. */
      if (maxKeyResponse < 200)    //higher threshold for sheathed touchpad
        return -1;
    
    /* We have a finger */

    /* Now set a grass cutting level, to remove small noise values from the
       fine position estimation. */
    grassLevel = (maxKeyResponse - minKeyResponse) >> 3;
    
    /* We need to determine which of the two possible areas of the pad the finger is in, based on the
       maximum response. */
    /* Find the two possible positions */
    keyNo1 = mapSenseToKey[maxPos][0];
    keyNo2 = mapSenseToKey[maxPos][1];
    /* Try to make the finger flow, as a way to work out which of two possible areas is the real one */
    /* Treat vertical movement down through the cross specially */
    if (abs(peak - keyNo2) > abs(peak - keyNo1))
    {
        if (peak < 12  ||  keyNo1 != 2)
            keyNo2 = keyNo1;
    }
    tempKeyResponseOffset = 0;
    tempKeyResponse = 0;
    if (peak >= 12  &&  keyNo1 == 2)
    {
        /* We think the finger is moving vertically into the centre of the cross. This needs to be
           handled specially. */
        peak = 13;
        noiseFilter(4, 13);
        noiseFilter(1, 14);
        noiseFilter(0, 15);
    }
    else
    {
        /* Do a linear weighted average around the area where we think the finger is */
        /* In this case we think that the finger is at key 'j' */
        peak = keyNo2;
        keyNoOffset = keyNo2 - 1;
        if (keyNoOffset < 0)
            keyNoOffset = 0;
        maxKeyNo = keyNo2 + 1;
        if (maxKeyNo >= NUM_KEYS)
            maxKeyNo = NUM_KEYS - 1;
        for (keyNoIndex = keyNoOffset;  keyNoIndex <= maxKeyNo;  keyNoIndex++)
        {
            keyLineIndex = mapKeyToSense[keyNoIndex];
            noiseFilter(keyLineIndex, keyNoIndex + 1);
        }
    }
    
    *newKey = peak;
    
    keyResponseOffset = tempKeyResponseOffset;
    keyResponse = tempKeyResponse;

    keyResponse /= (keyResponseOffset >> 4);
    keyResponse -= 16;
    if (keyResponse < 0)
        keyResponse = 0;
    return keyResponse;
}//end findFingerPosition

void main(void)
{
    int keyLineIndex;
    int newKey = 0;

    WDTCTL = WDTPW | WDTHOLD;             /* Stop the watchdog */

    /* Use the VLOCLK oscillator. This is important. If we leave the unused 32kHz
       oscillator running, its pins will not be free for sensing. */
    BCSCTL3 = LFXT1S_2;
    BCSCTL1 = CALBC1_16MHZ;               /* Set the DCO speed */
    DCOCTL = CALDCO_16MHZ;

    /* We must turn off the low frequency crystal oscillator, as we are using its pins as I/O pins */
    _BIS_SR(OSCOFF);

    /* Initialise all the I/O pins */
    P1OUT = 0;
    P1DIR = 0xFF;
    P1SEL = 0;
    P1REN = 0;
    P2OUT = 0;
    P2DIR = 0xFF;
    P2SEL = 0;
    P2REN = 0;

    /* Prepare for I2C communication */
    usiI2cInit();

    _EINT();

    /* Initialise all the keys */
    for (keyLineIndex = 0;  keyLineIndex < NUM_LINES;  keyLineIndex++)
        initKey(&keyLine[keyLineIndex], &keyLineConfig[keyLineIndex]);

    /* Drive Timer A from the SMCLK, in continuous mode */
    TACTL = TASSEL_2 | MC_2;          // used higher TAR clock freq. if 
                                      // sheathed touchpad is used.
    
    /* Scan the keys quite a few times, to allow plenty of time for the
       MCLK and board conditions to stablise */
    for (keyLineIndex = 0;  keyLineIndex < 100;  keyLineIndex++)
        scanKeys();

    /* Now we can use the current filtered key response as the base response. */
    for (keyLineIndex = 0;  keyLineIndex < NUM_LINES;  keyLineIndex++)
    {
        keyLine[keyLineIndex].baseCapacitance = keyLine[keyLineIndex].filtered >> 4;
        keyLine[keyLineIndex].filtered = 0;
    }

    for (;;)
    {
        scanKeys();
        
        newPosition = findFingerPosition
        (
            &newKey
        );
              
        if (newPosition >= 0)
        {
            /* There is a finger on the pad */
              sendToHost(newKey);
        }
        else
        {
            /* There is no finger on the pad */
            lastPosition = -100;      
        }
        
    } //end for(;;)
} //end main

/* ----------------------------- KEY HANDLING INTERRUPTS ---------------------------- */

#pragma vector=PORT1_VECTOR
__interrupt void port_1_interrupt(void)
{
    P1IFG = 0;                                /* Clear interrupt flag */
    timerCount = TAR - timerCount;          /* Record the discharge time */
    LPM3_EXIT;                                /* Exit from low power 3 or 0 */
}

#pragma vector=PORT2_VECTOR
__interrupt void port_2_interrupt(void)
{
    P2IFG = 0;                                /* Clear interrupt flag */
    timerCount = TAR - timerCount;          /* Record the discharge time */
    LPM3_EXIT;                                /* Exit from low power 3 or 0 */
}

#pragma vector=TIMERA0_VECTOR
__interrupt void timera0_interrupt(void)
{
    LPM3_EXIT;
}

#pragma vector=TIMERA1_VECTOR
__interrupt void timera1_interrupt(void)
{
    switch (TAIV)
    {
    case 2:
        /* TACCR1 */
        LPM3_EXIT;
        break;
    }
}

/* ------------------------------- COMMUNICATIONS ---------------------- */

const uint8_t SLV_Addr = 0x48;                      /* I2C slave address is 0x48 */
int8_t I2C_state = -2;                               /* I2C state tracking */

/* function to send data to FG4618 */
void sendToHost(int data)
{
    hostData = data;
    I2C_state = 0;
    USICTL1 |= USIIFG;                              /* Set flag and start communication */
    /* Wait until the I/O operation has completed */
    do
        LPM0;
    while (I2C_state >= 0);
}

/* function to set up I2C communication */
void usiI2cInit(void)
{
    P1OUT |= (BIT7 | BIT6);                         /* P1.6 & P1.7 are for I2C */
    P1REN |= (BIT7 | BIT6);                         /* Enable P1.6 & P1.7 pullups */

    USICTL0 = USIPE6 | USIPE7 | USIMST | USISWRST;  /* Port & USI mode setup */
    USICTL1 = USII2C | USIIE;                       /* Enable I2C mode & USI interrupt */
    USICKCTL = USIDIV_7 | USISSEL_2 | USICKPL;      /* Setup USI clock: SCL = SMCLK/128 (~125kHz) */
    USICNT |= USIIFGCC;                             /* Disable automatic clear control */
    USICTL0 &= ~USISWRST;                           /* Enable USI */
    USICTL1 &= ~USIIFG;                             /* Clear pending flag */
}

#pragma vector = USI_VECTOR
__interrupt void USI_TXRX(void)
{
    switch (__even_in_range(I2C_state, 10))
    {
    case 0:
         /* Generate start condition & send address to slave */
        P1OUT |= 0x01;                      /* LED on: sequence start */
        USISRL = 0x00;                      /* Generate Start Condition... */
        USICTL0 |= (USIGE | USIOE);
        USICTL0 &= ~USIGE;
        USISRL = SLV_Addr << 1;             /* ... and transmit address, R/W = 0 */
        USICNT = (USICNT & 0xE0) + 0x08;    /* Bit counter = 8, TX Address */
        I2C_state = 2;                      /* Go to next state: receive address (N)Ack */
        break;
    case 2:
        /* Receive sddress Ack/Nack bit */
        USICTL0 &= ~USIOE;                  /* SDA = input */
        USICNT |= 0x01;                     /* Bit counter = 1, receive (N)Ack bit */
        I2C_state = 4;                      /* Go to next state: check (N)Ack */
        break;
    case 4:
        /* Process sddress Ack/Nack & handle data TX */
        USICTL0 |= USIOE;                   /* SDA = output */
        if (USISRL & 0x01)
        {
            /* Nack received. Send stop... */
            USISRL = 0x00;
            USICNT |=  0x01;                /* Bit counter = 1, SCL high, SDA low */
            I2C_state = 10;                 /* Go to next state: generate Stop */
            P1OUT |= 0x01;                  /* Turn on LED: error */
        }
        else
        {
            /* Ack received, TX data to slave... */
            USISRL = hostData;             /* Load data byte */
            USICNT |=  0x08;                /* Bit counter = 8, start TX */
            I2C_state = 6;                  /* Go to next state: receive data (N)Ack */
            P1OUT &= ~0x01;                 /* Turn off LED */
        }
        break;
    case 6:
        /* Receive Data Ack/Nack bit */
        USICTL0 &= ~USIOE;                  /* SDA = input */
        USICNT |= 0x01;                     /* Bit counter = 1, receive (N)Ack bit */
        I2C_state = 8;                      /* Go to next state: check (N)Ack */
        break;
    case 8:
        /* Process Data Ack/Nack & send Stop */
        USICTL0 |= USIOE;
        if (USISRL & 0x01)
        {
            /* Nack received... */
            /* TODO: Handle the NACK */
        }
        else
        {
            /* Ack received */
        }
        /* Send stop... */
        USISRL = 0x00;
        USICNT |=  0x01;                    /* Bit counter = 1, SCL high, SDA low */
        I2C_state = 10;                     /* Go to next state: generate stop */
        break;
    case 10:
        /* Generate Stop Condition */
        USISRL = 0x0FF;                     /* USISRL = 1 to release SDA */
        USICTL0 |= USIGE;                   /* Transparent latch enabled */
        USICTL0 &= ~(USIGE | USIOE);        /* Latch/SDA output disabled */
        I2C_state = -2;                     /* Reset state machine for next transmission */
        LPM0_EXIT;                          /* Exit active for next transfer */
        break;
    }
    USICTL1 &= ~USIIFG;                     /* Clear pending flag */
}
